package com.crazy.web.interceptor.mybatis;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import com.crazy.logs.Logger;
import com.github.foxnic.sql.expr.Expr;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据源拦截器
 * @author zhaochaofeng
 */
@Slf4j
@AllArgsConstructor
@Intercepts(value = {
        @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class })
})
@Component
public class DataScopeInterceptor extends AbstractSqlParserHandler implements Interceptor {

    /**
     * 有意义的包名
     */
    private final String myPackage = "com.crazy";

    /**
     * 拦截所有select语句，打印出来，并且获取执行时间
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object object = invocation.getArgs()[1];
        // sqlId为mapper文件中定义的id，例如：com.**.dao.**Mapper.selectByPrimaryKey
        String sqlId = mappedStatement.getId();
        // 开始时间
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } catch (Exception e) {
            log.error(sqlId + "执行失败！", e);
            return null;
        } finally {
            // 取得当前栈信息
            StringBuilder sb = sqlRoute(start);
            long end = System.currentTimeMillis();
            long time = end - start;
            sb.append("\n消耗时间 ");
            sb.append(time);
            sb.append(" ms.");
            //执行时间超2s，通过warn打印到控制台
            if(time>2000){
                log.warn(sb.toString(), MDC.get(Logger.REQUEST_IP), MDC.get(Logger.TIRACE_ID_KEY), mappedStatement.getId(), getCompleteSql(mappedStatement, object));
            }else{
                log.info(sb.toString(), MDC.get(Logger.REQUEST_IP), MDC.get(Logger.TIRACE_ID_KEY), mappedStatement.getId(), getCompleteSql(mappedStatement, object));
            }
        }
    }

    /**
     * 获取sql的路由
     * @return
     */
    private StringBuilder sqlRoute(long start) {
        StackTraceElement[] stes = new Throwable().getStackTrace();
        Stack runClasses = new Stack();
        for(StackTraceElement ste : stes){
            if(ste.getClassName().contains(myPackage) && !ste.getClassName().equals(this.getClass().getName()) && !ste.getClassName().contains("$$")){
                StringBuilder sb = new StringBuilder();
                sb.append("类");
                sb.append(ste.getClassName());
                sb.append(".");
                sb.append(ste.getMethodName());
                sb.append("\n");
                runClasses.push(sb.toString());
            }
        }
        StringBuilder str = new StringBuilder();
        str.append("\n请求IP:{},请求线程({})");
        str.append("\n");
        while(!runClasses.isEmpty()) {
            str.append(runClasses.pop());
        }
        str.append("类{}");
        str.append("\n执行SQL: \n[\n{}\n]");
        return str;
    }

    /**
     * 读取完整的SQL
     * @return
     */
    private String getCompleteSql(MappedStatement mappedStatement, Object object) {
        BoundSql boundSql = mappedStatement.getBoundSql(object);
        List<ParameterMapping> pms = boundSql.getParameterMappings();
        List<String> properties = pms.stream().map(s -> {return s.getProperty();}).collect(Collectors.toList());
        //参数处理
        Object[] objs = properties.stream().map(s -> {
            Object param = null;
            if(object instanceof Map) {
                String[] strs = s.split("\\.");
                Object query = ((Map) object).get(strs[0]);
                //处理QueryWrapper的请求
                if(query instanceof QueryWrapper){
                    Object pnvp = ((QueryWrapper<?>) query).getParamNameValuePairs();
                    if(pnvp instanceof Map){
                        param = ((Map)pnvp).get(strs[2]);
                    }
                }
            }else{
                //判定参数是否来自Model
                if(object instanceof Model){
                    return BeanUtil.getFieldValue(object, s);
                }else{
                    return object;
                }
            }
            return param;
        }).toArray();
        Expr se=new Expr(boundSql.getSql(), objs);
        return se.getSQL();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
